#![allow(unused_variables)]
#![allow(unused_imports)]


use std::collections::HashMap ;
use anyhow::{anyhow, Result} ;
use serde_json::Value as JsonValue ;
use crate::schema::{Schema, TypeName, DefaultValue, 
  parse_avro_json, build_type_environment, parse_fullname , render_fullname, type_name
} ;
use tracing::{info, debug, trace} ;

pub fn decode_avro_json(schema: &Schema, json: &JsonValue) -> Result<DefaultValue> {
    fn missing(name: &TypeName) -> Result<Schema> {
        Err(anyhow!(format!("Type {:?} not in schema", name)))
    }
    fn is_built_in(name: &str) -> bool {
        vec!["null", "boolean", "int", "long", "float", "double", "bytes", "string", "array", "map"].contains(&name)
    }
    fn env(schema: &Schema, name: &TypeName) -> Result<Schema> {
        build_type_environment(missing, schema, name)
    }

    fn patch_nullable(t: &Schema, v: &JsonValue) -> Result<DefaultValue> {
        match t {
            Schema::Union {options, ..} => {
                let branches = options.iter().map(type_name).collect::<Vec<_>>() ;
                let branch_schema = match options.as_slice() {
                    [Schema::Null, schema] => Some(schema),
                    [schema, Schema::Null] => Some(schema),
                    _ => None
                };
                match branch_schema {
                    Some(t) => {
                        let nested = parse_avro_json(&union, &env, t, v)? ;
                        Ok(DefaultValue::DUnion(options.to_owned(), t.to_owned(), Box::new(nested)))
                    },
                    None => {
                        Err(anyhow!(format!("union branch not find: {:?}", branches)))
                    }
                }
            },
            _ => Err(anyhow!(format!("Impossible: not a Union ")))
        }
    }
    fn union(schema: &Schema, json: &JsonValue) -> Result<DefaultValue> { 
        match (schema, json) {
            (Schema::Union{ options, .. }, JsonValue::Null) => {
                if options.contains(&Schema::Null) {
                    Ok(DefaultValue::DUnion(options.to_owned(), Schema::Null, Box::new(DefaultValue::DNull)))
                } else {
                Err(anyhow!(format!("Null not in union.")))
                }
            },
            (t@Schema::Union{ options, .. }, v@JsonValue::Object(obj)) => {
                if obj.is_empty() {
                    Err(anyhow!(format!("Invalid encoding of union: empty object ({{}}).")))
                // } else if obj.len() > 1 {
                //    Err(anyhow!(format!("Invalid encoding of union: object with too many fields.")))
                } else {
                    fn canonicalize(name: &str) -> String {
                        if is_built_in(name) {
                        name.to_owned()
                        } else  {
                            render_fullname(&parse_fullname(name))
                        }
                    } 
                    let names = options.iter().map(|s| (type_name(&s), s.clone())).collect::<HashMap<_,_>>() ;
                    if obj.len() == 1 {
                        let branch = obj.keys().next().unwrap().to_string() ;
                        debug!("union => parse unions: {:?} with json branch: {:?}", names.keys(), branch) ;
                        match names.get(&canonicalize(&branch)) {
                            Some(branch_schema) => {
                                let nested = parse_avro_json(&union, &env, branch_schema, &v[&branch])? ;
                                Ok(DefaultValue::DUnion(options.to_owned(), t.to_owned(), Box::new(nested)))
                            },
                            None => {
                                patch_nullable(t, v)
                            }
                        }
                    } else {
                        patch_nullable(t, v)
                    }
                }
            },
            (t@Schema::Union{ options, .. }, v) => {
                patch_nullable(t, v)
            },
            (_, _) => Err(anyhow!(format!("Impossible: function given non-union schema.")))
        }
    }

    parse_avro_json(&union, &env, schema, json)
}

